#ifndef XG_WEBX_ROUTE_CPP
#define XG_WEBX_ROUTE_CPP
///////////////////////////////////////////////////////////
#include "../route.h"
#include "../../dbentity/T_XG_CONF.h"
#include "../../dbentity/T_XG_DBETC.h"
#include "../../dbentity/T_XG_GROUP.h"
#include "../../dbentity/T_XG_ACCESS.h"

webx::AccessItem::AccessItem(const string& param, const vector<string>& groupset)
{
	this->access = XG_AUTHFAIL;
	this->param = param;

	if (param.length() > 0) node.parse(stdx::trim(param));

	if (groupset.size() > 0)
	{
		access = XG_FAIL;

		for (const string& item : groupset)
		{
			if (item == "public")
			{
				access = XG_OK;

				break;
			}

			group.insert(item);
		}
	}
}
int webx::AccessItem::check(const string& param, const vector<string>& grouplist) const
{
	if (node.size() > 0)
	{
		const string& tmp = stdx::trim(param);
		const map<string, string>& map = node.getDataMap();

		if (tmp.front() == '{' && tmp.back() == '}')
		{
			JsonElement data(tmp);

			if (data.size() < node.size()) return XG_FAIL;

			for (auto& item : map)
			{
				if (data.asString(item.first) != item.second) return XG_FAIL;
			}
		}
		else
		{
			HttpDataNode data;

			data.parse(tmp);

			if (data.size() < node.size()) return XG_FAIL;

			for (auto& item : map)
			{
				if (data.getValue(item.first) != item.second) return XG_FAIL;
			}
		}
	}

	if (access) return access;

	for (const string& item : grouplist)
	{
		if (group.find(item) != group.end()) return XG_OK;
	}

	return XG_AUTHFAIL;
}

bool webx::NeedUpdateRouteHost()
{
	typedef BOOL (*NeedUpdateRouteHostFunc)();
	static NeedUpdateRouteHostFunc func = (NeedUpdateRouteHostFunc)Process::GetObject("HTTP_NEED_UPDATE_ROUTE_HOST_FUNC");

	return func && func();
}

void webx::InitNewUser(const string& user)
{
	typedef void (*AsyncInitNewUserFunc)(const char*);
	static AsyncInitNewUserFunc func = (AsyncInitNewUserFunc)Process::GetObject("HTTP_ASYNC_INIT_NEW_USER");

	if (func) func(user.c_str());
}

string webx::GetParameter(const string& id)
{
	typedef int (*GetParameterFunc)(const char*, char*, int);
	static GetParameterFunc func = (GetParameterFunc)Process::GetObject("HTTP_GET_PARAMETER");

	if (func == NULL) return stdx::EmptyString();

	char buffer[8 * 1024];
	
	if (func(id.c_str(), buffer, sizeof(buffer)) > 0) return buffer;

	return stdx::EmptyString();
}

sp<DBConnect> webx::GetDBConnect(const string& dbid)
{
	static HttpServer* app = HttpServer::Instance();

	if (dbid.empty())
	{
		sp<DBConnect> conn = app->getDBConnect();
		
		if (!conn)
		{
			LogTrace(eERR, "connect database failed");

			stdx::Throw(XG_SYSBUSY, "connect database failed");
		}

		return conn;
	}
	else
	{
		typedef DBConnectPool* (*GetDBConnectPoolFunc)(const char*);
		static GetDBConnectPoolFunc func = (GetDBConnectPoolFunc)Process::GetObject("HTTP_GET_DBCONNECT_POOL");

		if (func == NULL)
		{
			LogTrace(eERR, "connect database[" + dbid + "] failed");

			stdx::Throw(XG_SYSERR, "connect database failed");
		}

		DBConnectPool* pool = func(dbid.c_str());

		if (pool == NULL)
		{
			LogTrace(eERR, "connect database[" + dbid + "] failed");

			stdx::Throw(XG_SYSERR, "connect database failed");
		}

		sp<DBConnect> conn = pool->get();
		
		if (!conn)
		{
			LogTrace(eERR, "grasp database[" + dbid + "] connection failed");

			stdx::Throw(XG_SYSBUSY, "connect database failed");
		}

		return conn;
	}
}

bool webx::LoadAccessMap(map<string, vector<AccessItem>>& accessmap)
{
	CT_XG_ACCESS tab;
	map<string, vector<AccessItem>> tabaccessmap;

	try
	{
		tab.init(webx::GetDBConnect());
	}
	catch(...)
	{
		return false;
	}

	CHECK_FALSE_RETURN(tab.find("1=1"));

	while (tab.next())
	{
		tabaccessmap[tab.path.val()].push_back(AccessItem(tab.param.val(), {}));
	}

	for (auto& item : tabaccessmap)
	{
		std::swap(accessmap[item.first], item.second);
	}

	struct Param
	{
		string param;
		vector<string> group;
	};

	CT_XG_GROUP grptab;
	map<string, vector<Param>> grpmap;
	map<string, vector<AccessItem>> grptabaccessmap;

	grptab.init(webx::GetDBConnect());

	CHECK_FALSE_RETURN(grptab.find("1=1"));

	while (grptab.next())
	{
		string param;
		vector<string> pathlist = stdx::split(grptab.pathlist.toString(), ",");

		if (pathlist.empty()) continue;

		for (string path : pathlist)
		{
			size_t pos = path.find('?');

			if (pos == string::npos)
			{
				param.clear();
			}
			else
			{
				param = path.substr(pos + 1);
				path = path.substr(0, pos);
			}

			bool notfound = true;
			vector<Param>& vec = grpmap[path];

			for (Param& data : vec)
			{
				if (param == data.param)
				{
					data.group.push_back(grptab.id.val());
					notfound = false;

					break;
				}
			}

			if (notfound)
			{
				Param item;

				item.param = param;
				item.group.push_back(grptab.id.val());

				vec.push_back(item);
			}
		}
	}

	for (auto& item : grpmap)
	{
		vector<AccessItem>& vec = grptabaccessmap[item.first];

		for (Param& data : item.second)
		{
			vec.push_back(AccessItem(data.param, data.group));
		}
	}

	for (auto& item : grptabaccessmap)
	{
		std::swap(accessmap[item.first], item.second);
	}

	static HttpServer* app = HttpServer::Instance();
	static string cfgpath = app->getConfigFile()->getFilePath();

	if (cfgpath.length() > 0)
	{
		size_t pos;
		string path;
		string param;
		ConfigFile file;
		SmartBuffer content;
		map<string, string> cfgmap;
		map<string, vector<AccessItem>> cfgaccessmap;

		CHECK_FALSE_RETURN(app->getFileContent(cfgpath, content) > 0);
		CHECK_FALSE_RETURN(file.parse(content.str()));

		file.getMapData(cfgmap);

		for (auto& item : cfgmap)
		{
			if (item.first.length() > 7 && memcmp(item.first.c_str(), "access@", 7) == 0)
			{
				path = item.first.substr(7);
				pos = path.rfind('?');

				if (pos == string::npos)
				{
					param.clear();
				}
				else
				{
					param = path.substr(pos + 1);
					path = path.substr(0, pos);
				}

				cfgaccessmap[path].push_back(AccessItem(param, stdx::split(item.second, ",")));
			}
		}

		for (auto& item : cfgaccessmap)
		{
			std::swap(accessmap[item.first], item.second);
		}
	}

	for (auto& item : accessmap)
	{
		vector<AccessItem>& vec = item.second;

		std::sort(vec.begin(), vec.end(), [](const AccessItem& a, const AccessItem& b){
			return a.getWeight() > b.getWeight();
		});
	}

	return true;
}

int webx::GetLastRemoteStatus()
{
	typedef int (*GetRemoteStatusFunc)();
	static GetRemoteStatusFunc func = (GetRemoteStatusFunc)Process::GetObject("HTTP_GET_LAST_REMOTE_STATUS_FUNC");

	return func ? func() : XG_FAIL;
}
HostItem webx::GetLogHost()
{
	int port;
	char host[64];
	HostItem item;
	typedef int (*GetLogHostFunc)(char*, int*);
	static GetLogHostFunc func = (GetLogHostFunc)Process::GetObject("HTTP_GET_LOG_HOST_FUNC");

	if (func == NULL) return item;

	if (func(host, &port) > 0)
	{
		item.host = host;
		item.port = port;
	}

	return item;
}
HostItem webx::GetRegCenterHost()
{
	int port;
	char host[64];
	HostItem item;
	typedef int (*GetRegCenterHostFunc)(char*, int*);
	static GetRegCenterHostFunc func = (GetRegCenterHostFunc)Process::GetObject("HTTP_GET_REG_CENTER_HOST_FUNC");

	if (func == NULL) return item;

	if (func(host, &port) > 0)
	{
		item.host = host;
		item.port = port;
	}

	return item;
}

void webx::CheckSystemRight(ProcessBase* proc)
{
	try
	{
		proc->checkLogin();
		proc->checkSystemRight();
	}
	catch(Exception e)
	{
		HostItem route = GetRegCenterHost();
		const string& path = proc->getRequest()->getPath();
		const string& host = proc->getResponse()->getClientHost();

		if (host.empty()) return stdx::Throw(XG_AUTHFAIL, "permission denied");

		if (IsLocalHost(host.c_str()))
		{
			LogTrace(eIMP, "request[%s] from local host", path.c_str());
		}
		else if (route.equals(host))
		{
			LogTrace(eIMP, "request[%s] from route center", path.c_str());
		}
		else
		{
			sp<DBConnect> dbconn = webx::GetDBConnect();
			sp<QueryResult> rs = dbconn->query("SELECT ID FROM T_XG_ROUTE WHERE HOST=? AND ENABLED>0", host);

			if (rs && rs->next())
			{
				LogTrace(eIMP, "request[%s] from trust host[%s]", path.c_str(), host.c_str());
			}
			else
			{
				throw e;
			}
		}
	}
}

HostItem webx::GetRouteHost(const string& path)
{
	int port;
	char host[64];
	HostItem item;
	typedef int (*GetRouteHostFunc)(const char*, char*, int*);
	static GetRouteHostFunc func = (GetRouteHostFunc)Process::GetObject("HTTP_GET_ROUTE_HOST_FUNC");

	if (func == NULL) return item;

	if (func(path.c_str(), host, &port) > 0)
	{
		item.host = host;
		item.port = port;
	}

	return item;
}

int webx::SetLogHost(const string& host, int port)
{
	typedef int (*SetLogHostFunc)(const char*, int);
	static SetLogHostFunc func = (SetLogHostFunc)Process::GetObject("HTTP_SET_LOG_HOST_FUNC");

	if (func == NULL) return XG_SYSERR;

	return func(host.c_str(), port);
}

int webx::UpdateRouteList(const string& host, int port)
{
	typedef int (*UpdateRouteListFunc)(const char*, int);
	static UpdateRouteListFunc func = (UpdateRouteListFunc)Process::GetObject("HTTP_UPDATE_ROUTE_LIST_FUNC");

	if (func == NULL) return XG_SYSERR;

	return func(host.c_str(), port);
}

SmartBuffer webx::GetRemoteResult(const string& path, const string& param, const string& contype, const string& cookie)
{
	typedef SmartBuffer (*GetRemoteResultFunc)(const char*, const char*, const char*, const char*);
	static GetRemoteResultFunc func = (GetRemoteResultFunc)Process::GetObject("HTTP_GET_REMOTE_RESULT_FUNC");

	if (func == NULL) return SmartBuffer();

	return func(path.c_str(), param.c_str(), contype.c_str(), cookie.c_str());
}

void webx::GetRemoteResult(const string& path, JsonReflect& response, const Object& param, const string& contype, const string& cookie)
{
	SmartBuffer data = GetRemoteResult(path, param.toString(), contype, cookie);

	if (data.str() && response.fromString(data.str())) return;

	int code = GetLastRemoteStatus();

	stdx::Throw(code < 0 ? code : XG_ERROR, "grasp remote request[" + path + "] failed");
}

int webx::CheckAccess(const IHttpRequest* request, const string& grouplist)
{
	return CheckAccess(request->getPath(), request->getDataString(), grouplist);
}

int webx::CheckAccess(const string& path, const string& param, const string& grouplist)
{
	typedef int (*CheckAccessFunc)(const char*, const char*, const char*);
	static CheckAccessFunc func = (CheckAccessFunc)Process::GetObject("HTTP_CHECK_ACCESS_FUNC");

	if (func == NULL) stdx::Throw(XG_SYSERR);

	int code = func(path.c_str(), param.c_str(), grouplist.c_str());

	if (code < 0) stdx::Throw(code);

	return code;
}

int webx::Broadcast(const string& path, const string& param, const string& contype, const string& cookie)
{
	typedef int (*BroadcastFunc)(const char*, const char*, const char*, const char*);
	static BroadcastFunc func = (BroadcastFunc)Process::GetObject("HTTP_BROADCAST_HOST_FUNC");

	if (func == NULL) return XG_SYSERR;

	return func(path.c_str(), param.c_str(), contype.c_str(), cookie.c_str());
}

int webx::NotifyHost(const string& host, int port, const string& path, const string& param, const string& contype, const string& cookie)
{
	typedef int (*NotifyHostFunc)(const char*, int, const char*, const char*, const char*, const char*);
	static NotifyHostFunc func = (NotifyHostFunc)Process::GetObject("HTTP_NOTIFY_HOST_FUNC");

	if (func == NULL) return XG_SYSERR;

	return func(host.c_str(), port, path.c_str(), param.c_str(), contype.c_str(), cookie.c_str());
}

typedef TSMap<string, sp<ConfigFile>> ConfileMap;

static ConfileMap* GetConfileMap()
{
	XG_DEFINE_GLOBAL_VARIABLE(ConfileMap);
}

static string GetConfileKey(const string& name)
{
	return "SYSTEM_CONFILE[" + name + "]";
}

void webx::RemoveConfileCache(const string& name)
{
	if (name.empty())
	{
		GetConfileMap()->clear();
	}
	else
	{
		GetConfileMap()->remove(GetConfileKey(name));
	}
}

sp<ConfigFile> webx::GetConfile(const string& name)
{
	string content;
	sp<ConfigFile> cfg;

	if (GetConfileMap()->get(GetConfileKey(name), cfg)) return cfg;

	if (cfg) return cfg;

	auto dbconn = webx::GetDBConnect();
	string sqlcmd = "SELECT CONTENT FROM T_XG_CONF WHERE FOLDER='WEBPAGE' AND TITLE=?";

	if (dbconn->select(content, sqlcmd, name) < 0) stdx::Throw(XG_SYSERR, "load confile[" + name + "] failed");

	if (content.empty())
	{
		int res = 0;
		CT_XG_CONF tab;
		JsonElement json(GetRemoteConfig("confile", name));

		tab.init(dbconn);

		tab.user = "system";
		tab.icon = "/res/img/note/code.png";
		tab.title = json["title"].asString();
		tab.folder = json["folder"].asString();
		tab.content = content = json["content"].asString();

		tab.level = 1;
		tab.statetime.update();

		for (int i = 0; i < 5; i++)
		{
			tab.id = DateTime::GetBizId();
			tab.position = (int)(time(NULL));

			if ((res = tab.insert()) >= 0) break;
		}

		if (res < 0) stdx::Throw(XG_SYSERR, "sync confile[" + name + "] failed");

		LogTrace(eIMP, "sync confile[%s] success", name.c_str());
	}

	cfg = newsp<ConfigFile>();

	cfg->parse(content);

	GetConfileMap()->set(GetConfileKey(name), cfg);

	return cfg;
}

string webx::GetConfig(const string& name, const string& key)
{
	static ConfigFile* confile = HttpServer::Instance()->getConfigFile();

	if (name.empty()) return confile->getVariable(key);
	if (key.empty()) return confile->getVariable(name);

	return GetConfile(name)->getVariable(key);
}

int webx::SyncRemoteConfig(const string& type, const string& name)
{
	auto sync = [&](const string& type){
		int num = 0;
		sp<DBConnect> dbconn = webx::GetDBConnect();

		if (type == "confile")
		{
			auto synconfile = [&](const string& name){
				JsonElement json(GetRemoteConfig(type, name));

				string title = json["title"].asString();
				string folder = json["folder"].asString();
				string content = json["content"].asString();

				if (dbconn->execute("UPDATE T_XG_CONF SET CONTENT=? WHERE FOLDER=? AND TITLE=?", content, folder, title) < 0)
				{
					LogTrace(eERR, "sync configure[confile][%s] failed", title.c_str());
				}
				else
				{
					LogTrace(eIMP, "sync configure[confile][%s] success", title.c_str());

					RemoveConfileCache(title);

					++num;
				}
			};

			if (name.empty())
			{
				sp<QueryResult> rs = dbconn->query("SELECT TITLE FROM T_XG_CONF WHERE FOLDER='WEBPAGE'");

				if (rs)
				{
					sp<RowData> row = rs->next();

					while (row)
					{
						string name = row->getString(0);

						row = rs->next();

						synconfile(name);
					}
				}
			}
			else
			{
				synconfile(name);
			}
		}
		else
		{
			JsonElement json(GetRemoteConfig(type, name));

			json = json.get("list");

			CHECK_FALSE_RETURN(json.isArray());

			for (auto item : json)
			{
				string id = item["id"].asString();

				if (name.empty() || id == name)
				{
					CT_XG_DBETC tab;

					tab.init(dbconn);

					for (auto tmp : item)
					{
						string key = tmp.getName();

						tab.setValue(stdx::toupper(key), tmp.asString());
					}

					if (tab.update() < 0)
					{
						LogTrace(eERR, "sync configure[database][%s] failed", id.c_str());

						return false;
					}

					LogTrace(eIMP, "sync configure[database][%s] success", id.c_str());

					typedef DBConnectPool* (*GetDBConnectPoolFunc)(const char*);

					static GetDBConnectPoolFunc getPool = (GetDBConnectPoolFunc)Process::GetObject("HTTP_GET_DBCONNECT_POOL");

					if (tab.find() && tab.next() && tab.enabled.val() > 0)
					{
						DBConnectPool* pool = getPool(id.c_str());

						if (pool)
						{
							ConfigFile cfg;
							string key = "DLLPATH_" + tab.type.val();
							string path = webx::GetParameter(stdx::toupper(key));

							if (path.empty())
							{
								path = stdx::tolower(tab.type.val());
								path = stdx::translate("$PRODUCT_HOME/dll/libdbx." + path + "pool.so");
							}

							cfg.setVariable("DLLPATH", path);
							cfg.setVariable("HOST", tab.host.val());
							cfg.setVariable("USER", tab.user.val());
							cfg.setVariable("NAME", tab.name.val());
							cfg.setVariable("PORT", (int)tab.port.val());
							cfg.setVariable("CHARSET", tab.charset.val());
							cfg.setVariable("PASSWORD", tab.passwd.val());

							pool->init(cfg);
						}

						++num;
					}
				}
			}
		}

		return true;
	};

	int res = 0;

	if (type.empty())
	{
		if (sync("database")) res++;
		if (sync("confile")) res++;
	}
	else
	{
		if (sync(type)) res++;
	}

	if (res == 0) stdx::Throw(XG_ERROR, "sync configure failed");

	if (type == "database") webx::ReloadSystemConfig();

	return res;
}

string webx::GetRemoteConfig(const string& type, const string& name)
{
	HostItem route = webx::GetRegCenterHost();

	for (int i = 0; i < 5 && route.host.empty(); i++)
	{
		sleep(1);

		route = webx::GetRegCenterHost();
	}

	if (route.host.empty()) stdx::Throw(XG_SYSERR, "route center not found");

	HttpRequest request;

	if (type == "database")
	{
		request.init("/getrecordlist");

		request.setParameter("id", name);
		request.setParameter("tabid", "${database}");
		request.setParameter("pagesize", "10000000");
	}
	else if (type == "confile")
	{
		request.init("/confile/sharenote");

		request.setParameter("flag", "Q");
		request.setParameter("title", name);
	}
	else
	{
		stdx::Throw(XG_PARAMERR, "invalid parameter[type]");
	}

	SmartBuffer data = request.getResult(route.host, route.port);

	if (data.isNull()) stdx::Throw(XG_NETERR, "request[" + type + "] failed");

	return data.str();
}
///////////////////////////////////////////////////////////
#endif
